﻿/*
 * Sdp.cpp
 *
 *  Created on: 2013年12月6日
 *      Author: zhengchuanjiang
 */

#include "Sdp.h"
#include "Sdp.h"
#include <map>
#include <vector>
#include "TStringCast.h"
#include "TStringUtil.h"


namespace sdp
{

namespace detail
{


}


using namespace detail;


////////////////////////////////////////////////////////////////////////////
class ConstName
{
public:
    static const char* get(SendRecvMode mode);

    static const char* recvonly;
    static const char* sendrecv;
    static const char* sendonly;
    static const char* inactive;

};

const char* ConstName::get(SendRecvMode mode)
{
    if (mode == kSendRecv)
    {
        return "sendrecv";
    }
    else if (mode == kRecvOnly)
    {
        return "recvonly";
    }
    else if (mode == kSendonly)
    {
        return "sendonly";
    }
    else if (mode == kInactive)
    {
        return "inactive";
    }
    return "";
}


const char* ConstName::recvonly = "recvonly";
const char* ConstName::sendrecv = "sendrecv";
const char* ConstName::sendonly = "sendonly";
const char* ConstName::inactive = "inactive";


////////////////////////////////////////////////////////////////////////////

SendRecvMode findMode(const Attributes& attr)
{
    SendRecvMode mode = kSendRecv;
    if (attr.exists(ConstName::recvonly))
    {
        mode = kRecvOnly;
    }
    else if (attr.exists(ConstName::sendonly))
    {
        mode = kSendonly;
    }
    else if (attr.exists(ConstName::inactive))
    {
        mode = kInactive;
    }
    return mode;
}

const char* getName(SendRecvMode mode)
{
    return ConstName::get(mode);
}


Origin::Origin(const std::string& user, int sess,
        int ver, AddrType type, const std::string& ip):
        username(user),
        session(),
        version(),
        addr(type, ip)
{
    session = comn::StringCast::toString(sess);
    version = comn::StringCast::toString(ver);
}

////////////////////////////////////////////////////////////////////////////

Attributes::Attributes()
{

}

Attributes::~Attributes()
{
}

Attributes::Attributes(const Attributes& other)
{
    m_map = other.m_map;
}

Attributes& Attributes::operator = (const Attributes& other)
{
    m_map = other.m_map;
    return *this;
}


bool Attributes::operator == (const Attributes& other)
{
    return (m_map == other.m_map);
}

bool Attributes::operator < (const Attributes& other)
{
    return (m_map < other.m_map);
}


void Attributes::swap(Attributes& other)
{
    m_map.swap(other.m_map);
}

bool Attributes::set(const std::string& key, const std::string& value)
{
    std::pair< StringMap::iterator, bool > pairb =
            m_map.insert(StringMap::value_type(key, value));
    if (!pairb.second)
    {
        (*pairb.first).second = value;
    }
    return pairb.second;
}

std::string& Attributes::operator [](const char* key)
{
    return m_map[key];
}

std::string& Attributes::operator [](const std::string& key)
{
    return m_map[key];
}


size_t Attributes::size() const
{
    return m_map.size();
}


bool Attributes::empty() const
{
    return m_map.empty();
}


bool Attributes::getAt(size_t idx, std::string& value) const
{
    if (idx >= size())
    {
        return false;
    }

    StringMap::const_iterator it = m_map.begin();
    std::advance(it, idx);

    value = it->second;

    return true;
}

bool Attributes::getAt(size_t idx, std::string& key, std::string& value) const
{
    if (idx >= size())
    {
        return false;
    }

    StringMap::const_iterator it = m_map.begin();
    std::advance(it, idx);

    value = it->second;
    key = it->first;

    return true;
}

bool Attributes::find(const std::string& key, std::string& value) const
{
    bool found = false;
    StringMap::const_iterator it = m_map.find(key);
    if (it != m_map.end())
    {
        value = it->second;
    }
    return found;
}

bool Attributes::find(const char* key, std::string& value) const
{
    bool found = false;
    StringMap::const_iterator it = m_map.find(key);
    if (it != m_map.end())
    {
        value = it->second;
        found = true;
    }
    return found;
}

bool Attributes::find(const char* key, int64_t& value) const
{
    bool found = false;
    StringMap::const_iterator it = m_map.find(key);
    if (it != m_map.end())
    {
        comn::StringCast::toValue(it->second, value);
        found = true;
    }
    return found;
}

bool Attributes::find(const char* key, int& value) const
{
    bool found = false;
    StringMap::const_iterator it = m_map.find(key);
    if (it != m_map.end())
    {
        comn::StringCast::toValue(it->second, value);
        found = true;
    }
    return found;
}

bool Attributes::find(const char* key, double& value) const
{
    bool found = false;
    StringMap::const_iterator it = m_map.find(key);
    if (it != m_map.end())
    {
        comn::StringCast::toValue(it->second, value);
        found = true;
    }
    return found;
}

bool Attributes::exists(const std::string& key) const
{
    StringMap::const_iterator it = m_map.find(key);
    return (it != m_map.end());
}

bool Attributes::exists(const char* key) const
{
    StringMap::const_iterator it = m_map.find(key);
    return (it != m_map.end());
}


void Attributes::clear()
{
    m_map.clear();
}


bool Attributes::erase(const std::string& key)
{
    return m_map.erase(key) > 0;
}


bool Attributes::erase(const char* key)
{
    return m_map.erase(key) > 0;
}

bool Attributes::findWithValue(const std::string& value, std::string& key) const
{
    bool found = false;
    StringMap::const_iterator it = m_map.begin();
    for (; it != m_map.end(); it ++)
    {
        if (it->second == value)
        {
            key = it->first;
            found = true;
            break;
        }
    }
    return found;
}

////////////////////////////////////////////////////////////////////////////
int StrUtil::icompare(const std::string& src, const char* target)
{
    return comn::StringUtil::icompare(src, target);
}

int StrUtil::split(const std::string& src, char sp, std::string& head, std::string& tail)
{
    if (src.empty())
    {
        return 0;
    }

    size_t pos = src.find(sp);
    if (pos != std::string::npos)
    {
        head = src.substr(0, pos);
        tail = src.substr(pos+1);
        return 2;
    }
    head = src;
    return 1;
}

int StrUtil::split(const std::string& src, char sp, size_t start,
            std::string& head, std::string& tail)
{
    if (src.empty())
    {
        return 0;
    }

    size_t pos = src.find(sp, start);
    if (pos != std::string::npos)
    {
        head = src.substr(start, pos - start);
        tail = src.substr(pos+1);
        return 2;
    }
    head = src.substr(start);
    return 1;
}

bool StrUtil::tail(const std::string& src, char sp, std::string& str)
{
    size_t idx = src.find(sp);
    if (idx == std::string::npos)
    {
        return false;
    }

    str = src.substr(idx + 1);
    return true;
}

bool StrUtil::head(const std::string& src, char sp, std::string& value)
{
    size_t idx = src.find(sp);
    if (idx == std::string::npos)
    {
        value = src;
        return false;
    }

    value = src.substr(0, idx);
    return true;
}

bool StrUtil::tail(const std::string& src, char sp, int& value)
{
    bool done = false;
    std::string str;
    if (tail(src, sp, str))
    {
        comn::StringCast::toValue(str, value);
        done = true;
    }
    return done;
}




StringToken::StringToken(const std::string& src, char sp, size_t start):
        m_src(src),
        m_sp(sp),
        m_pos(start)
{

}

bool StringToken::next(std::string& section)
{
    if (m_pos == std::string::npos)
    {
        return false;
    }

    size_t nextPos = m_src.find(m_sp, m_pos);
    if (nextPos == std::string::npos)
    {
        section.assign(m_src, m_pos, std::string::npos);
        m_pos = std::string::npos;
    }
    else
    {
        section.assign(m_src, m_pos, (nextPos - m_pos));
        m_pos = nextPos + 1;
    }
    return true;
}

bool StringToken::next(int& value)
{
    bool done = false;
    std::string section;
    if (next(section))
    {
        comn::StringCast::toValue(section, value);
        done = true;
    }
    return done;
}


bool AttributeParser::parse(const std::string& str, char eol, char equal, Attributes& attr)
{
    StringToken token(str, eol, 0);
    std::string line;
    while (token.next(line))
    {
        std::string key;
        std::string value;
        StrUtil::split(line, equal, key, value);

        attr[key] = value;
    }
    return true;
}

//////////////////////////////////////////////////////////////////////////
bool H264Fmtp::parse(const std::string& line)
{
    Attributes attr;
    if (!AttributeParser::parse(line, ';', '=', attr))
    {
        return false;
    }

    attr.find("packetization-mode", packetization_mode);
    attr.find("profile-level-id", profile_level_id);
    attr.find("sprop-parameter-sets", sprop_parameter_sets);
    return true;
}

bool AacFmtp::parse(const std::string& line)
{
    Attributes attr;
    if (!AttributeParser::parse(line, ';', '=', attr))
    {
        return false;
    }

    attr.find("mode", mode);
    attr.find("streamtype", streamType);
    attr.find("indexdeltalength", indexDeltaLength);
    attr.find("indexlength", indexLength);
    attr.find("sizelength", sizeLength);
    attr.find("config", config);
    return true;
}

////////////////////////////////////////////////////////////////////////////

const char* Medium::VIDEO = "video";
const char* Medium::AUDIO = "audio";
const char* Medium::TEXT = "text";
const char* Medium::APPLICATION = "application";
const char* Medium::MESSAGE = "message";

size_t Medium::findCodec(const std::string& name) const
{
    for (size_t i = 0; i < codecs.size(); i ++)
    {
        if (codecs[i].name == name)
        {
            return i;
        }
    }
    return -1;
}

size_t Medium::match(const Codec& codec) const
{
    return findCodec(codec.name);
}

size_t Medium::match(const CodecArray& arrCodec) const
{
    for (size_t i = 0; i < arrCodec.size(); ++ i)
    {
        size_t idx = match(arrCodec[i]);
        if (idx != (size_t)-1)
        {
            return idx;
        }
    }
    return -1;
}

size_t Medium::match(const Medium& medium) const
{
    return match(medium.codecs);
}

void Medium::clear()
{
    key.clear();
    formats.clear();
    codecs.clear();
    attributes.clear();
}

////////////////////////////////////////////////////////////////////////////
SessionDescription::SessionDescription():
        version(0),
        origin(),
        name(),
        info(),
        uri(),
        email(),
        phone(),
        connection(),
        bandwidth(),
        time(),
        repeatTime(),
        timeZone(),
        key(),
        attributes(),
        mediums()
{

}

SessionDescription::~SessionDescription()
{
}


bool SessionDescription::parse(const char* str, size_t length)
{
    clear();

    size_t start = 0;
    size_t end = 0;
    std::string line;
    for (size_t i = 0; i < length; ++ i)
    {
        if (str[i] == '\n')
        {
            if (i > 0 && str[i-1] == '\r')
            {
                end = i - 1;
            }
            else
            {
                end = i;
            }

            line.assign(str + start, end - start);
            parseLine(line);
            start = i + 1;
        }
    }

    if (start < length)
    {
        line.assign(str + start, length - start);
        parseLine(line);
    }

    return true;
}

bool SessionDescription::parse(const std::string& str)
{
    return parse(str.c_str(), str.length());
}

size_t SessionDescription::getMediumCount() const
{
    return mediums.size();
}

Medium& SessionDescription::getMedium(size_t idx)
{
    return mediums[idx];
}

void SessionDescription::clearMedium()
{
    mediums.clear();
}

void SessionDescription::addMedium(const Medium& media)
{
    mediums.push_back(media);
}


bool SessionDescription::parseLine(const std::string& line)
{
    if (line.empty())
    {
        return false;
    }

    bool done = true;
    switch (line[0])
    {
    case 'v':
        // version
        StrUtil::tail(line, '=', version);
        break;
    case 'o':
        // origin
        done = parseOrigin(line, origin);
        break;
    case 's':
        // session
        StrUtil::tail(line, '=', name);
        break;
    case 'i':
        // info
        StrUtil::tail(line, '=', info);
        break;
    case 'u':
        // uri
        StrUtil::tail(line, '=', uri);
        break;
    case 'e':
        // email
        StrUtil::tail(line, '=', email);
        break;
    case 'p':
        // phone
        StrUtil::tail(line, '=', phone);
        break;
    case 'c':
        // connection
        if (hasMedium())
        {
            Medium& mediaDesc = getCurMedia();
            done = parseAddr(line, mediaDesc.connection);
        }
        else
        {
            done = parseAddr(line, connection);
        }
        break;
    case 'b':
        // bandwidth
        if (hasMedium())
        {
            Medium& mediaDesc = getCurMedia();
            done = parseBandwidth(line, mediaDesc.bandwidth);
        }
        else
        {
            done = parseBandwidth(line, bandwidth);
        }
        break;
    case 'z':
        // time zone
        StrUtil::tail(line, '=', timeZone);
        break;
    case 'k':
        // key
        if (hasMedium())
        {
            Medium& mediaDesc = getCurMedia();
            done = parseKey(line, mediaDesc.key);
        }
        else
        {
            done = parseKey(line, key);
        }
        break;
    case 't':
        // time
        done = parseTime(line, time);
        break;
    case 'r':
        // repeat
        StrUtil::tail(line, '=', repeatTime);
        break;
    case 'm':
        // media
        done = parseMedia(line);
        break;
    case 'a':
        // attribute
        if (hasMedium())
        {
            Medium& mediaDesc = getCurMedia();
            done = parseMediaAttribute(line, mediaDesc);
        }
        else
        {
            done = parseAttribute(line, attributes);
        }
        break;

    default:
        done = false;
        break;
    }

    return done;
}

bool SessionDescription::parseOrigin(const std::string& line, Origin& origin)
{
    StringToken token(line, ' ', 2);
    token.next(origin.username);
    token.next(origin.session);
    token.next(origin.version);
    token.next(origin.addr.nettype);

    token.next(origin.addr.addrtype);

    token.next(origin.addr.address);

    return true;
}

bool SessionDescription::parseTime(const std::string& line, Time& timeDesc)
{
    std::string head;
    std::string tail;
    StrUtil::split(line, ' ', 2, head, tail);
    comn::StringCast::toValue(head, timeDesc.start);
    comn::StringCast::toValue(tail, timeDesc.stop);
    return true;
}

bool SessionDescription::parseAddr(const std::string& line, Connection& conn)
{
    StringToken token(line, ' ', 2);
    token.next(conn.nettype);
    token.next(conn.addrtype);

    std::string addr;
    token.next(addr);

    std::string ttl;
    StrUtil::split(addr, '/', conn.address, ttl);

    comn::StringCast::toValue(ttl, conn.ttl);

    return true;
}

bool SessionDescription::parseBandwidth(const std::string& line, Bandwidth& bandwidth)
{
    std::string tail;
    StrUtil::split(line, ':', 2, bandwidth.bwtype, tail);
    comn::StringCast::toValue(tail, bandwidth.bandwidth);
    return true;
}


bool SessionDescription::parseKey(const std::string& line, EncryptionKey& key)
{
    std::string tail;
    StrUtil::split(line, ':', 2, key.method, key.key);
    return true;
}

bool SessionDescription::parseMedia(const std::string& line)
{
    mediums.push_back(Medium());
    Medium& mediaDesc = mediums.back();
    return parseMedia(line, mediaDesc);
}

bool SessionDescription::parseAttribute(const std::string& line, Attributes& attr)
{
    bool done = false;
    std::string name;
    std::string value;
    if (parseAttribute(line, name, value))
    {
        attr[name] = value;
        done = true;
    }
    return done;
}

bool SessionDescription::parseMedia(const std::string& line, Medium& mediaDesc)
{
    StringToken token(line, ' ', 2);
    token.next(mediaDesc.name);
    std::string sec;
    token.next(sec);
    comn::StringCast::toValue(sec, mediaDesc.port);
    token.next(mediaDesc.proto);

    while (token.next(sec))
    {
        int format = 0;
        comn::StringCast::toValue(sec, format);

        mediaDesc.formats.push_back(format);
    }

    return true;
}

bool SessionDescription::parseAttribute(const std::string& line, std::string& name, std::string& value)
{
    std::string tail;
    StrUtil::split(line, ':', 2, name, value);
    return true;
}

bool SessionDescription::parseMediaAttribute(const std::string& line, Medium& mediaDesc)
{
    bool done = false;
    std::string name;
    std::string value;
    if (parseAttribute(line, name, value))
    {
        if (name == "rtpmap")
        {
            Codec codec;
            parseRtpmap(value, codec);
            mediaDesc.codecs.push_back(codec);
        }
        else if (name == "fmtp")
        {
            if (!mediaDesc.codecs.empty())
            {
                Codec& codec = mediaDesc.codecs.back();
                std::string pt;
                StrUtil::split(value, ' ', pt, codec.encParam);
            }
        }
        else if (name == "control")
        {
            mediaDesc.control = value;
        }
        else
        {
            mediaDesc.attributes.set(name, value);
        }

        done = true;
    }
    return done;
}

bool SessionDescription::parseRtpmap(const std::string& str, Codec& codec)
{
    std::string strPayload;
    std::string section;
    StrUtil::split(str, ' ', strPayload, section);

    comn::StringCast::toValue(strPayload, codec.payload);

    StringToken token(section, '/');
    token.next(codec.name);
    token.next(codec.rate);
    token.next(codec.param);
    //token.next(codec.param);
    return true;
}

Medium& SessionDescription::getCurMedia()
{
    return mediums.back();
}

bool SessionDescription::hasMedium() const
{
    return mediums.size() > 0;
}

void SessionDescription::clear()
{
    version = 0;
    //origin;
    name.clear();
    info.clear();
    uri.clear();
    email.clear();
    phone.clear();

    connection.clear();
    bandwidth.clear();

    time.clear();
    repeatTime.clear();
    timeZone.clear();
    key.clear();

    attributes.clear();

    mediums.clear();
}

SendRecvMode SessionDescription::getMode() const
{
    return findMode(attributes);
}


bool SessionDescription::getRange(double& start, double& stop)
{
    std::string strRange;
    if (!attributes.find("range", strRange))
    {
        return false;
    }

    std::string strTime;
    StrUtil::tail(strRange, '=', strTime);
    std::string strStart;
    std::string strStop;
    StrUtil::split(strTime, '-', strStart, strStop);
    
    comn::StringCast::toValue(strStart, start);
    comn::StringCast::toValue(strStop, stop);
    return true;
}

std::string SessionDescription::toString() const
{
    std::ostringstream oss;

    oss << *this;

    return oss.str();
}

std::ostream& SessionDescription::output(std::ostream& os) const
{
    os << *this;
    return os;
}


std::ostream& operator << (std::ostream& os, const Bandwidth& bw)
{
    os << bw.bwtype << ":" << bw.bandwidth;
    return os;
}

std::ostream& operator << (std::ostream& os, const Origin& origin)
{
    os << origin.username << " " << origin.session << " " << origin.version
            << " " << origin.addr;
    return os;
}

std::ostream& operator << (std::ostream& os, const Attributes& attr)
{
    std::string key;
    std::string value;
    for (size_t i = 0; i < attr.size(); ++ i)
    {
        attr.getAt(i, key, value);

        os << "a=" << key;
        if (!value.empty())
        {
            os << ":" << value << "\r\n";
        }
        else
        {
            os << "\r\n";
        }
    }
    return os;
}

std::ostream& operator << (std::ostream& os, const Time& t)
{
    os << t.start << " " << t.stop;
    return os;
}

std::ostream& operator << (std::ostream& os, const Connection& conn)
{
    os << conn.nettype << " " << conn.addrtype << " " << conn.address;
    if (conn.ttl >= 0)
    {
        os << "/" << conn.ttl;
    }
    return os;
}

std::ostream& operator << (std::ostream& os, const Codec& codec)
{
    os << "a=rtpmap:" << codec.payload << " " << codec.name << "/" << codec.rate;
    if (!codec.param.empty())
    {
        os << "/" << codec.param;
    }
    os << "\r\n";

    if (!codec.encParam.empty())
    {
        os << "a=fmtp:" << codec.payload << " " << codec.encParam;
        os << "\r\n";
    }

    return os;
}

std::ostream& operator << (std::ostream& os, const Medium& medium)
{
    os << "m=" << medium.name << " " << medium.port << " " << medium.proto;
    for (size_t i = 0; i < medium.formats.size(); ++ i)
    {
        os << " " << medium.formats[i];
    }
    os << "\r\n";

    for (size_t i = 0; i < medium.codecs.size(); ++ i)
    {
        os << medium.codecs[i];
    }

    os << medium.attributes;

    return os;
}

std::ostream& operator << (std::ostream& os, const SessionDescription& session)
{
    os << "v=" << session.version << "\r\n";
    os << "o=" << session.origin << "\r\n";
    os << "s=" << session.name << "\r\n";
    os << "i=" << session.info << "\r\n";
    os << "c=" << session.connection << "\r\n";

    if (!session.bandwidth.bwtype.empty())
    {
        os << "b=" << session.bandwidth << "\r\n";
    }

    if (!session.uri.empty())
    {
        os << "u=" << session.uri << "\r\n";
    }
    if (!session.email.empty())
    {
        os << "e=" << session.email << "\r\n";
    }
    if (!session.phone.empty())
    {
        os << "p=" << session.phone << "\r\n";
    }
    

    os << "t=" << session.time << "\r\n";

    os << session.attributes;

    for (size_t i = 0; i < session.mediums.size(); ++ i)
    {
        os << session.mediums[i];
    }

    return os;
}

size_t SessionDescription::findMedium(const std::string& name)
{
    for (size_t i = 0; i < mediums.size(); ++ i)
    {
        if (mediums[i].name == name)
        {
            return i;
        }
    }
    return -1;
}






} /* namespace net */
